<?php
namespace Plu\Library\Plugins;

use Phalcon\Mvc\User\Plugin;

class HttpResponsePlugin extends Plugin {

    /**
     * {@inheritdoc}
     */
    public function beforeExecuteRoute($event, $dispatcher) {
        $annotations = $this->annotations->getMethod(
            $dispatcher->getControllerClass(),
            $dispatcher->getActiveMethod()
        );

        if ($annotations->has('checkLogin')) {
            $annotation = $annotations->get('checkLogin');
            $this->checkLogin($annotation);
        }

        if ($annotations->has('cache')) {
            $annotation = $annotations->get("cache");
            return $this->beforeCache($annotation, $event, $dispatcher);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function afterExecuteRoute($event, $dispatcher) {
        $annotations = $this->annotations->getMethod(
            $dispatcher->getControllerClass(),
            $dispatcher->getActiveMethod()
        );

        if ($annotations->has('cache_control')) {
            $annotation = $annotations->get('cache_control');
            $this->setResponseCache($annotation);
        }

        if ($annotations->has('cache')) {
            $annotation = $annotations->get("cache");
            $this->afterCache($annotation, $event, $dispatcher);
        }
    }

    /**
     * 检查用户是否登录
     */
    private function checkLogin($annotation) {
        if (!$this->passport) {
            return false;
        }

        $isLogined = $this->passport->isLogined();
        if ($isLogined <= 0) {
            $callback = $annotation->getNamedArgument('callback');
            if ($callback) {

            } else {
                return $this->throwStatus406();
            }
        }
    }

    private function throwStatus406() {
        $this->response->setStatusCode(406, "Not Acceptable");
        $this->response->send();
        exit;
    }

    /**
     * 设定缓存头信息
     * @param public=true
     * @param private=true
     * @param no_cache=true
     * @param no_transform=true
     * @param must_revalidate=true
     * @param proxy_revalidate=true
     * @param max_age=seconds
     */
    private function setResponseCache($annotation) {
        $options = $annotation->getArguments();
        if ($options) {
            $options = array_shift($options);
            $this->response->setHeader('Cache-Control', $options);
        }
    }

    /**
     * 缓存返回的view results结果
     *
     * @param prefix 缓存key前缀
     * @param method 缓存参数, query: _GET请求的queryString,  post: post参数
     * @param ignore 屏蔽guid字段 
     * @param ttl    缓存时长
     */
    private $allowCacheType = array(
        "query",
        "get",
        "post"
    );

    private function parseCacheArguments($annotation) {
        //参数获取
        $prefix         = $annotation->getNamedParameter('prefix');
        $method         = $annotation->getNamedParameter("method");
        $ignoreFields   = $annotation->getNamedParameter("ignore");
        $ttl            = (int)$annotation->getNamedParameter("ttl");

        if (empty($prefix)) {
            return;
        }

        if ($ttl <= 0) {
            return;
        }

        if (!in_array($method, $this->allowCacheType)) {
            $method = "query";
        }

        if (!is_array($ignoreFields)) {
            $ignoreFields = [ $ignoreFields ];
        }

        $args = [];

        $suffix = "";
        switch ($method) {
            case 'post':
                //post 需要进行组合才能合成  getQuery() + getPost() + getRawBody()
                //with cookie?
                $queryData = $this->request->getQuery();
                $queryData = array_merge($queryData, $this->request->getPost());
                for ($i = 0, $len = count($ignoreFields); $i < $len; $i++) {
                    unset($queryData[$ignoreFields[$i]]);
                }
                $suffix = md5( json_encode($queryData) . $this->request->getRawBody());
                break;
            case 'query':
            case 'get':
                $queryData = $this->request->getQuery();
                for ($i = 0, $len = count($ignoreFields); $i < $len; $i++) {
                    unset($queryData[$ignoreFields[$i]]);
                }
                $suffix = md5(json_encode($queryData));
                break;
        }

        $guid = sprintf("%s_%s", $prefix, $suffix);

        return [$guid, $ttl];
    }

    private function beforeCache($annotation, $event, $dispatcher) {
        list($guid, $ttl) = $this->parseCacheArguments($annotation);

        if (empty($guid)) {
            return;
        }

        $data = $this->cache->get($guid, $ttl);

        if (!empty($data)) {
            $headers = $data['headers'];
            $headers->set("X-SS-Cache", "Hit");
            if (!empty($headers)) {
                $this->response->resetHeaders();
                $this->response->setHeaders($headers);
            }

            $content = $data['content'];

            if ($this->request->has('callback')) {
                $content = sprintf("%s(%s)", $this->request->get('callback'), $content);
            }

            $this->response->setContent($content);
            $this->response->send();

            if (is_callable([$event, 'isCancelable'])) {
                if ($event->isCancelable()) {
                    $event->stop();
                }
            }
            return false;
        }
    }

    private function afterCache($annotation) {
        list($guid, $ttl) = $this->parseCacheArguments($annotation);

        if (empty($guid)) {
            return;
        }

        //保存内容包含 headers 
        $content = $this->response->getContent();
        $headers = $this->response->getHeaders();

        // 如果content为空不做任何处理
        if (empty($content)) {
            return true;
        }
        //处理json/jsonp/html各种形式
        //尝试获取callback参数
        if ($this->request->has('callback')) {
            $callback = $this->request->get('callback');
            $content = ltrim($content, "$callback(");
            $content = rtrim($content, ")");
        }

        $this->response->setHeader("X-SS-Cache", "Miss");

        $data = [
            'content' => $content,
            'headers' => $headers
        ];

        $this->cache->save($guid, $data, $ttl);
    }
}
